package com.malty.gateway.sign;

import com.malty.common.ErrorCode;
import com.malty.common.exception.BusinessException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;

import java.util.*;

/**
 * @author malty
 */
public class ApiSignStrategyDispatcher {
    private static final Map<MediaType, ApiSignStrategy> MEDIA_TYPE_API_SIGN_STRATEGY_MAP;
    private static final QueryParamSignStrategy QUERY_PARAM_SIGN_STRATEGY;
    private static final EmptySignStrategy EMPTY_SIGN_STRATEGY;

    static {
        Map<MediaType, ApiSignStrategy> strategies = new HashMap<>();
        strategies.put(MediaType.APPLICATION_JSON, new PostJsonApiSignStrategy());
        strategies.put(MediaType.APPLICATION_FORM_URLENCODED, new FormUrlEncodedApiSignStrategy());
        MEDIA_TYPE_API_SIGN_STRATEGY_MAP = Collections.unmodifiableMap(strategies);

        QUERY_PARAM_SIGN_STRATEGY = new QueryParamSignStrategy();
        EMPTY_SIGN_STRATEGY = new EmptySignStrategy();
    }

    /**
     * 异步选择签名策略。
     * 对于GET方法，返回QueryParamSignStrategy。
     * 对于DELETE/HEAD/OPTIONS/TRACE方法，返回EmptySignStrategy。
     * 对于POST/PUT等有请求体的方法，根据Content-Type和请求体是否为空来选择策略。
     *
     * @param exchange 请求
     * @return Mono<ApiSignStrategy>
     */
    public static ApiSignStrategy selectStrategy(ServerWebExchange exchange) {
        ServerHttpRequest httpRequest = exchange.getRequest();
        String method = httpRequest.getMethodValue().toUpperCase();

        switch (method) {
            case "GET":
                return QUERY_PARAM_SIGN_STRATEGY;
            case "DELETE":
            case "HEAD":
            case "OPTIONS":
            case "TRACE":
                return EMPTY_SIGN_STRATEGY;
            default:
                // 处理有请求体的方法（POST, PUT, PATCH等）
                HttpHeaders headers = httpRequest.getHeaders();
                MediaType contentType = headers.getContentType();
                return Optional.ofNullable(MEDIA_TYPE_API_SIGN_STRATEGY_MAP.get(contentType))
                        .orElseThrow(() -> new BusinessException(ErrorCode.OPERATION_ERROR, "不支持的签名"));
        }
    }
}
